package com.gooluke.redis.uitl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类
 */
@Component
public class RedisUtil {

    private static final Logger log = LoggerFactory.getLogger(RedisUtil.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // region 查询缓存
    public <T> T get(String key, Class<T> clazz) {
        AssertUtil.notEmpty(key, "key不能为空");
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }

        // 使用 Jackson 的 ObjectMapper 进行转换
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(value, clazz);
    }

    /**
     * 复杂类型数据转换
     *
     * @param key           key
     * @param typeReference typeReference
     * @param <T>           返回类型
     * @return T
     */
    public <T> T get(String key, TypeReference<T> typeReference) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(value, typeReference);
    }

    public String getString(String key) {
        AssertUtil.notEmpty(key, "key不能为空");
        return stringRedisTemplate.opsForValue().get(key);
    }
    // endregion
    // region 设置缓存

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean set(String key, Object value, long time) {
        try {
            AssertUtil.isTrue(time > 0, "过期时间必须大于0");
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setNx(String key, Object value) {
        try {
            Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
            return result != null && result;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setNx(String key, Object value, long time) {
        try {
            AssertUtil.isTrue(time > 0, "过期时间必须大于0");
            Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            return result != null && result;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setString(String key, String value) {
        try {
            stringRedisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setString(String key, String value, long time) {
        try {
            AssertUtil.isTrue(time > 0, "过期时间必须大于0");
            stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setStringNx(String key, String value) {
        try {
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
            return result != null && result;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    public boolean setStringNx(String key, String value, long time) {
        try {
            AssertUtil.isTrue(time > 0, "过期时间必须大于0");
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            return result != null && result;
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }

    // endregion
    // region 过期时间
    public boolean expire(String key, long time) {
        AssertUtil.isTrue(time > 0, "过期时间必须大于0");
        redisTemplate.expire(key, time, TimeUnit.SECONDS);
        return true;
    }

    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    // endregion
    // region 是否存在key
    public boolean hasKey(String key) {
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            log.error("Redis operate error:", e);
            return false;
        }
    }
    // endregion
    // region 删除缓存

    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(Arrays.asList(key));
            }
        }
    }

    //删除允许失败，不影响业务逻辑
    public void delQuietly(String... key) {
        try {
            del(key);
        } catch (Exception e) {
            log.error("Redis operate error:", e);
        }
    }
    // endregion
    // region 自增、自减

    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // endregion
    // region 分布式锁

    // ============================== Distributed Lock ==============================

    /**
     * 尝试获取分布式锁
     *
     * @param key    锁的key
     * @param value  锁的value(建议使用UUID)
     * @param expire 过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String key, String value, long expire) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expire, TimeUnit.SECONDS));
    }

    /**
     * 释放分布式锁
     *
     * @param key   锁的key
     * @param value 锁的value
     * @return 是否释放成功
     */
    public boolean releaseLock(String key, String value) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(key), value);
        return result != null && result == 1;
    }

    // endregion
    // region Lua Script

    // ============================== Lua Script ==============================

    /**
     * 执行Lua脚本
     *
     * @param script Lua脚本
     * @param keys   key列表
     * @param args   参数列表
     * @return 执行结果
     */
    public <T> T executeScript(String script, List<String> keys, Object... args) {
        RedisScript<T> redisScript = new DefaultRedisScript<>(script);
        return redisTemplate.execute(redisScript, keys, args);
    }

    /**
     * 执行Lua脚本(带返回值类型)
     *
     * @param script     Lua脚本
     * @param resultType 返回值类型
     * @param keys       key列表
     * @param args       参数列表
     * @return 执行结果
     */
    public <T> T executeScript(String script, Class<T> resultType, List<String> keys, Object... args) {
        RedisScript<T> redisScript = new DefaultRedisScript<>(script, resultType);
        return redisTemplate.execute(redisScript, keys, args);
    }
    // endregion
}